#include "abc_archive.h"
#include <iostream>
#include "abc_query/query_builder.h"
#include "task/task_engine.h"

using namespace task;

namespace abc
{
    void testPredicates(const abc::AbcArchive::Ptr &archive)
    {
        auto nodes1 = abc_query::QueryBuilder()
                          .module("utils")
                          .package("/utils/date")
                          .findNodes(archive);
        std::cout << "Found " << nodes1.size() << " class nodes" << std::endl;

        auto functions1 = abc_query::QueryBuilder()
                              .functionName("getLocation")
                              .findFunctions(archive);

        std::cout << "Found " << functions1.size() << " getLocation functions" << std::endl;

        auto query1 = abc_query::QueryBuilder()
                          .module("entry");

        auto query2 = abc_query::QueryBuilder()
                          .module("utils");

        auto nodes2 = query1.Or(query2)
                          .nodeType(AbcNodeType::CLASS)
                          .findNodes(archive);

        std::cout << "Found " << nodes2.size() << " class nodes" << std::endl;

        auto funcQuery = abc_query::QueryBuilder()
                             .module("entry")
                             .functionName("getLocation");

        auto classQuery = abc_query::QueryBuilder()
                              .module("entry")
                              .functionName("getLocation");

        // 查询函数
        auto functions = funcQuery.findFunctions(archive);
        // 查询类
        auto classes = classQuery.findNodes(archive);

        std::cout << "Found " << functions.size() << " onClick functions" << std::endl;
        std::cout << "Found " << classes.size() << " Test classes" << std::endl;
    }

    struct Context1
    {
        AbckitFile *file;
        AbckitCoreImportDescriptor *desc;
    };

    struct GraphContext
    {
        AbckitGraph *graph;
    };

    static void *getImportDesc1(AbckitCoreFunction *func, Context1 *context)
    {
        AbckitCoreModule *module = abckitApi->function.functionGetModule(func);

        abckitApi->import.moduleEnumerateImports(module, context, [](AbckitCoreImportDescriptor *i, void *context)
                                                 {
            std::string importName = abckitApi->string.getImportDescriptorName(i);
            if(importName == "MyAspect"){
                Context1* cb = reinterpret_cast<Context1*>(context);
                cb->desc = i;
                std::cout << "找到import" << importName << std::endl;
            }
            return true; });
    }

    void testOriTaskBE(const abc::AbcArchive::Ptr &archive, GraphContext *graphContext)
    {
        auto funcs = abc_query::QueryBuilder().functionName("add").findFunctions(archive);
        for (auto abcFun : funcs)
        {
            std::cout << abckitApi->string.getFuncPath(abcFun) << std::endl;
            auto func = abcFun->coreFunction;
            auto graph = abckitApi->graph.createGraphFromFunction(func);
            graphContext->graph = graph;
            AbckitBasicBlock *startBB = abckitApi->inst.gGetStartBasicBlock(graph);
            std::vector<AbckitBasicBlock *> succBBs;
            abckitApi->inst.bbVisitSuccBlocks(startBB, &succBBs, [](AbckitBasicBlock *bb, void *succBB)
                                              {
                auto *succs = reinterpret_cast<std::vector<AbckitBasicBlock *> *>(succBB);
                succs -> emplace_back(bb);
                return true; });
            auto *succBB = succBBs[0];

            Context1 context;
            getImportDesc1(func, &context);

            auto *ldexternalmodulevar = abckitApi->isa.iCreateLdexternalmodulevar(graph, context.desc);
            abckitApi->inst.bbAddInstFront(succBB, ldexternalmodulevar);

            auto *myAspect = abckitApi->isa.iCreateThrowUndefinedifholewithname(graph, ldexternalmodulevar, abckitApi->string.createString(archive->file(), "MyAspect", strlen("MyAspect")));
            abckitApi->inst.iInsertAfter(myAspect, ldexternalmodulevar);

            auto *addBefore = abckitApi->isa.iCreateLdobjbyname(graph, myAspect, abckitApi->string.createString(archive->file(), "addBefore", strlen("addBefore")));
            abckitApi->inst.iInsertAfter(addBefore, myAspect);

            auto *call = abckitApi->isa.iCreateCallthis0(graph, addBefore, myAspect);
            abckitApi->inst.iInsertAfter(call, addBefore);

            std::cout << "func:" << func << "funcGraph:" << graph << std::endl;
            auto graph1 = abckitApi->graph.createGraphFromFunction(func);
            auto graph2 = abckitApi->graph.createGraphFromFunction(func);
            std::cout << "重新创建graph:" << graph1 << "  " << graph2 << std::endl;
        }
    }

    void testOriTaskAE(const abc::AbcArchive::Ptr &archive, GraphContext *graphContext)
    {
        auto funcs = abc_query::QueryBuilder().functionName("add").findFunctions(archive);
        for (auto abcFun : funcs)
        {
            std::cout << abckitApi->string.getFuncPath(abcFun) << std::endl;
            auto func = abcFun->coreFunction;
            auto graph = graphContext->graph;
            std::cout << "func:" << func << "funcGraph:" << graph << std::endl;
            Context1 context;
            context.file = archive->file();
            getImportDesc1(func, &context);

            abckitApi->graph.gVisitBlocksRpo(graph, &context, [](AbckitBasicBlock *basicBlock, void *data)
                                             {
                auto graph = abckitApi->inst.bbGetGraph(basicBlock);
                std::cout << "bbGraph:" << graph << std::endl;
                auto *inst = abckitApi->inst.bbGetFirstInst(basicBlock);
                auto *context = reinterpret_cast<Context1 *>(data);
                while (inst != nullptr) {
                    if (abckitApi->inst.iGetOpcode(inst) == ABCKIT_ISA_API_DYNAMIC_OPCODE_RETURNUNDEFINED 
                    || abckitApi->inst.iGetOpcode(inst) == ABCKIT_ISA_API_DYNAMIC_OPCODE_RETURN) {
                        
                        // auto *undef = abckitApi->inst.iGetPrev(inst);
                        auto *ldexternalmodulevar = abckitApi->isa.iCreateLdexternalmodulevar(graph, context->desc);
                        abckitApi->inst.iInsertBefore(ldexternalmodulevar,inst);

                        auto *myAspect = abckitApi->isa.iCreateThrowUndefinedifholewithname(graph, ldexternalmodulevar, abckitApi->string.createString(context->file, "MyAspect", strlen("MyAspect")));
                        abckitApi->inst.iInsertAfter(myAspect, ldexternalmodulevar);

                        auto *addBefore = abckitApi->isa.iCreateLdobjbyname(graph, myAspect, abckitApi->string.createString(context->file, "addBefore", strlen("addBefore")));
                        abckitApi->inst.iInsertAfter(addBefore, myAspect);

                        auto *call = abckitApi->isa.iCreateCallthis0(graph, addBefore, myAspect);
                        abckitApi->inst.iInsertAfter(call, addBefore);

                    }
                    inst = abckitApi->inst.iGetNext(inst);
                }

            return true; });

            abckitApi->function.functionSetGraph(func, graph);
        }
    }

    void testTask(const abc::AbcArchive::Ptr &archive)
    {
        InstEngine engine(archive);

        auto task1 = std::make_shared<TaskParam>();
        NodeParam node1;
        node1.function = "add";
        task1->nodeParam = &node1;
        task1->taskType = TaskType::BEFORE_EXECUTION;
        BasicApiInfo apiInfo;
        apiInfo.objName = "MyAspect";
        apiInfo.propName = "beforeExecution";
        task1->instApi = &apiInfo;
        engine.registerTask(task1);

        auto task2 = std::make_shared<TaskParam>();
        NodeParam node2;
        node2.function = "add";
        task2->nodeParam = &node2;
        task2->taskType = TaskType::BEFORE_EXECUTION;
        BasicApiInfo apiInfo2;
        apiInfo2.objName = "MyAspect";
        apiInfo2.propName = "beforeExecution";
        task2->instApi = &apiInfo2;
        engine.registerTask(task2);

        auto task3 = std::make_shared<TaskParam>();
        NodeParam node3;
        node3.function = "add";
        task3->nodeParam = &node3;
        task3->taskType = TaskType::AFTER_EXECUTION;
        BasicApiInfo apiInfo3;
        apiInfo3.objName = "MyAspect";
        apiInfo3.propName = "afterExecution";
        task3->instApi = &apiInfo3;
        engine.registerTask(task3);

        auto task4 = std::make_shared<TaskParam>();
        NodeParam node4;
        node4.function = "demo1";
        task4->nodeParam = &node4;
        task4->taskType = TaskType::BEFORE_CALL;
        BasicApiInfo callSite4;
        callSite4.objName = "Cal";
        callSite4.propName = "add";
        task4->callSite = new ApiInfo(&callSite4);
        BasicApiInfo apiInfo4;
        apiInfo4.objName = "MyAspect";
        apiInfo4.propName = "beforeCall";
        task4->instApi = &apiInfo4;
        engine.registerTask(task4);

        engine.transform();
    }

    void test()
    {
        auto abc_path = "/app/codes/aspect-framework/tests/be/demo.abc";
        auto abcArchive = AbcArchiveFactory::create(abc_path);
        // testPredicates(abcArchive);
        testTask(abcArchive);
        // GraphContext graphContext;
        // testOriTaskBE(abcArchive, &graphContext);
        // testOriTaskAE(abcArchive, &graphContext);

        auto new_path = "/app/codes/aspect-framework/tests/be/demo_new.abc";
        abckitApi->io.writeAbc(abcArchive->file(), new_path);
        std::cout << abcArchive->filePath() << std::endl;
    }
}

int main(int argc, char **argv)
{
    abc::test();
    return 0;
}